﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.Xml;
using System.Drawing;
using LevelEditor.Objects.Tiles;
using LevelEditor.Objects;
using LevelEditor.Objects.Waypoints;
using LevelEditor.Components;
using LevelEditor.Components.Layers;
using LevelEditor.Util;
using LevelEditor.Dao;
using DialogsEditor;


namespace LevelEditor.App
{
    class SaveHelper
    {
        private LayerPanel layerPanel;
        private Library lib;

        public SaveHelper(LayerPanel layerPanel, Library lib)
        {
            this.layerPanel = layerPanel;
            this.lib = lib;
        }

        private XmlElement CreateCharNode(Character c, XmlDocument xmlDoc, bool isOnLayer = false, bool isPointer = false)
        {

            if (isPointer)
            {
                XmlElement chara = xmlDoc.CreateElement("IdCharacter");
                chara.InnerText = c.ID.ToString();
                //chara.AppendChild(idC);

                return chara;
            }

            XmlElement tile = xmlDoc.CreateElement("Tile");

            XmlElement id = xmlDoc.CreateElement("ID");
            id.InnerText = c.ID.ToString();

            XmlElement tileName = xmlDoc.CreateElement("Name");
            tileName.InnerText = c.Name;

            XmlElement type = xmlDoc.CreateElement("Type");
            type.InnerText = c.myType.ToString();

            XmlElement locationX = xmlDoc.CreateElement(Character.Variables.PositionX.ToString());
            locationX.InnerText = c.PositionX.ToString();

            XmlElement locationY = xmlDoc.CreateElement(Character.Variables.PositionY.ToString());
            locationY.InnerText = c.PositionY.ToString();

            XmlElement fieldOfView = xmlDoc.CreateElement(Character.Variables.FieldOfView.ToString());
            fieldOfView.InnerText = c.FieldOfView.ToString();

            XmlElement perceptionArea = xmlDoc.CreateElement(Character.Variables.PerceptionArea.ToString());
            perceptionArea.InnerText = c.PerceptionArea.ToString();

            XmlElement walkSpeed = xmlDoc.CreateElement(Character.Variables.WalkSpeed.ToString());
            walkSpeed.InnerText = c.WalkSpeed.ToString();

            //Serializer o Trigger
            tile.AppendChild(id);
            tile.AppendChild(type);
            tile.AppendChild(tileName);            
            tile.AppendChild(locationX);
            tile.AppendChild(locationY);
            tile.AppendChild(fieldOfView);
            tile.AppendChild(perceptionArea);
            tile.AppendChild(walkSpeed);
            if (!isOnLayer)
                tile.AppendChild(CreateTriggerInfoNode(c, xmlDoc, false));
            tile.AppendChild(CreateImageInfoNode(c, xmlDoc));
            tile.AppendChild(CreateSandAloneBehaviorNode(c, xmlDoc));

            return tile;
        }

        private XmlElement CreateItemNode(Item i, XmlDocument xmlDoc, bool isOnLayer = false, bool isPointer = false)
        {
            if (isPointer)
            {
                XmlElement chara = xmlDoc.CreateElement("Item");

                XmlElement idC = xmlDoc.CreateElement("ID");
                idC.InnerText = i.ID.ToString();
                chara.AppendChild(idC);

                return chara;
            }

            XmlElement tile = xmlDoc.CreateElement("Tile");

            XmlElement id = xmlDoc.CreateElement("ID");
            id.InnerText = i.ID.ToString();

            XmlElement tileName = xmlDoc.CreateElement("Name");
            tileName.InnerText = i.Name;

            XmlElement type = xmlDoc.CreateElement("Type");
            type.InnerText = i.myType.ToString();

            XmlElement locationX = xmlDoc.CreateElement(Item.Variables.PositionX.ToString());
            locationX.InnerText = i.PositionX.ToString();

            XmlElement locationY = xmlDoc.CreateElement(Item.Variables.PositionY.ToString());
            locationY.InnerText = i.PositionY.ToString();

            tile.AppendChild(id);
            tile.AppendChild(type);
            tile.AppendChild(tileName);
            tile.AppendChild(locationX);
            tile.AppendChild(locationY);
            if (!isOnLayer)
                tile.AppendChild(CreateTriggerInfoNode(i, xmlDoc, true));
            tile.AppendChild(CreateImageInfoNode(i, xmlDoc));
            tile.AppendChild(CreateSandAloneBehaviorNode(i, xmlDoc));

            return tile;
        }

        private XmlElement CreateDoorNode(Door d, XmlDocument xmlDoc)
        {
            XmlElement tile = xmlDoc.CreateElement("Tile");

            XmlElement id = xmlDoc.CreateElement("ID");
            id.InnerText = d.ID.ToString();

            XmlElement tileName = xmlDoc.CreateElement("Name");
            tileName.InnerText = d.Name;

            XmlElement type = xmlDoc.CreateElement("Type");
            type.InnerText = d.myType.ToString();

            XmlElement locationX = xmlDoc.CreateElement(Door.Variables.PositionX.ToString());
            locationX.InnerText = d.PositionX.ToString();

            XmlElement locationY = xmlDoc.CreateElement(Door.Variables.PositionY.ToString());
            locationY.InnerText = d.PositionY.ToString();

            XmlElement nameRoom = xmlDoc.CreateElement(Door.Variables.NameRoom.ToString());
            nameRoom.InnerText = d.NameRoom.ToString();

            XmlElement isOpen = xmlDoc.CreateElement(Door.Variables.IsOpen.ToString());
            isOpen.InnerText = d.IsOpen.ToString();

            XmlElement isClosedForever = xmlDoc.CreateElement(Door.Variables.IsClosedForever.ToString());
            isClosedForever.InnerText = d.IsClosedForever.ToString();

            XmlElement isBlocked = xmlDoc.CreateElement(Door.Variables.IsBlocked.ToString());
            isBlocked.InnerText = d.IsBlocked.ToString();

            XmlElement loadNextLevel = xmlDoc.CreateElement(Door.Variables.LoadNextPartLevel.ToString());
            loadNextLevel.InnerText = d.LoadNextPartLevel.ToString();


            tile.AppendChild(id);
            tile.AppendChild(type);
            tile.AppendChild(tileName);
            tile.AppendChild(locationX);
            tile.AppendChild(locationY);
            tile.AppendChild(nameRoom);
            tile.AppendChild(isOpen);
            tile.AppendChild(isClosedForever);
            tile.AppendChild(isBlocked);
            tile.AppendChild(loadNextLevel);
            tile.AppendChild(CreateImageInfoNode(d, xmlDoc));
            tile.AppendChild(CreateSandAloneBehaviorNode(d, xmlDoc));

            return tile;
        }

        private XmlElement CreateCTileNode(CTile t, XmlDocument xmlDoc)
        {
            XmlElement tile = xmlDoc.CreateElement("Tile");

            XmlElement id = xmlDoc.CreateElement("ID");
            id.InnerText = t.ID.ToString();

            XmlElement tileName = xmlDoc.CreateElement("Name");
            tileName.InnerText = t.Name;

            XmlElement type = xmlDoc.CreateElement("Type");
            type.InnerText = t.myType.ToString();

            XmlElement standAloneBehavior = CreateSandAloneBehaviorNode(t, xmlDoc);

            tile.AppendChild(id);
            tile.AppendChild(type);
            tile.AppendChild(tileName);            
            tile.AppendChild(CreateImageInfoNode(t, xmlDoc));
            tile.AppendChild(standAloneBehavior);

            return tile;
        }

        private XmlElement CreateImageInfoNode(Tile tile, XmlDocument xmlDoc)
        {
            XmlElement imageInfo = xmlDoc.CreateElement("ImageInfo");

            imageInfo.InnerText = Convert.ToBase64String( tile.ImageBuffer);

            return imageInfo;
        }

        private XmlElement CreateCollisionDataNode(Behavior b, XmlDocument xmlDoc)
        {
            XmlElement collisionMap = xmlDoc.CreateElement("CollisionMap");

            for (int j = 0; j < b.Images.Count; j++)
            {
                for (int i = 0; i < b.CollisionMap.Count; i++)
                {
                    if (b.CollisionMap[i].ImageName != b.Name + j.ToString())
                    {
                        continue;
                    }
                    XmlElement imageIndex = xmlDoc.CreateElement("ImageIndex");
                    imageIndex.InnerText = j.ToString();

                    XmlElement boxes = xmlDoc.CreateElement("boxes");

                    XmlElement collisionData = xmlDoc.CreateElement("CollisionData");
                    
                    for (int k = 0; k < b.CollisionMap[i].Collisions.Count; k++)
                    {
                        XmlElement box = xmlDoc.CreateElement("box");

                        XmlElement width = xmlDoc.CreateElement("width");
                        width.InnerText = b.CollisionMap[i].Collisions[k].Width.ToString();

                        XmlElement height = xmlDoc.CreateElement("height");
                        height.InnerText = b.CollisionMap[i].Collisions[k].Height.ToString();

                        XmlElement positionX = xmlDoc.CreateElement("PositionX");
                        positionX.InnerText = b.CollisionMap[i].Collisions[k].Location.X.ToString();

                        XmlElement positionY = xmlDoc.CreateElement("PositionY");
                        positionY.InnerText = b.CollisionMap[i].Collisions[k].Location.Y.ToString();

                        box.AppendChild(width);
                        box.AppendChild(height);
                        box.AppendChild(positionX);
                        box.AppendChild(positionY);

                        boxes.AppendChild(box);
                    }

                    collisionData.AppendChild(imageIndex);
                    collisionData.AppendChild(boxes);
                    collisionMap.AppendChild(collisionData);
                }
                

                
            }

            return collisionMap;
        }

        private XmlElement CreateTriggerInfoNode(Tile tile, XmlDocument xmlDoc, bool isItem)
        {
            XmlElement myTrigger = xmlDoc.CreateElement("MyTrigger");

            for (int i = 0; i < tile.MyTrigger.Count; i++)
            {
                XmlElement trigger = xmlDoc.CreateElement("Trigger");

                XmlElement idTrigger = xmlDoc.CreateElement("Id");
                idTrigger.InnerText = tile.MyTrigger[i].ID.ToString();

                XmlElement nameTrigger = xmlDoc.CreateElement("NameTrigger");
                nameTrigger.InnerText = tile.MyTrigger[i].Name;

                XmlElement behavior = xmlDoc.CreateElement("MappedBehavior");
                if (tile.MyTrigger[i].MappedBehavior != null)
                    behavior = CreateMappedBehaviorInfoNode(tile.MyTrigger[i].MappedBehavior, behavior, xmlDoc, isItem);

                trigger.AppendChild(idTrigger);
                trigger.AppendChild(nameTrigger);
                trigger.AppendChild(behavior);

                myTrigger.AppendChild(trigger);
            }

            return myTrigger;
        }

        private XmlElement CreateMappedBehaviorInfoNode(Behavior b, XmlElement behaviorElement, XmlDocument xmlDoc, bool isItem)
        {
            XmlElement idBehavior = xmlDoc.CreateElement("Id");
            idBehavior.InnerText = b.ID.ToString();

            XmlElement nameBehavior = xmlDoc.CreateElement("Name");
            nameBehavior.InnerText = b.Name;

            if (isItem)
            {
                behaviorElement.AppendChild(idBehavior);
                behaviorElement.AppendChild(nameBehavior);
                return behaviorElement;
            }

            XmlElement wayPointCollection = xmlDoc.CreateElement("WaypointCollection");
            wayPointCollection = CreateWaypointInfoNode(b.Waypoints, wayPointCollection, xmlDoc);

            behaviorElement.AppendChild(idBehavior);
            behaviorElement.AppendChild(nameBehavior);
            behaviorElement.AppendChild(wayPointCollection);

            return behaviorElement;
        }

        private XmlElement CreateBehaviorInfoNode(Behavior b, XmlElement behaviorElement, XmlDocument xmlDoc)
        {
            XmlElement idBehavior = xmlDoc.CreateElement("IdBehavior");
            idBehavior.InnerText = b.ID.ToString();

            XmlElement nameBehavior = xmlDoc.CreateElement("NameBehavior");
            nameBehavior.InnerText = b.Name;

            XmlElement isWalk = xmlDoc.CreateElement("IsWalk");
            isWalk.InnerText = b.IsWalk.ToString();

            
            XmlElement spriteSheet = xmlDoc.CreateElement("SpriteSheet");
            if (b.IsSpriteSheet())
                spriteSheet.InnerText = Convert.ToBase64String(Util.Util.ConvertImageToByte(b.SpriteSheet));

            XmlElement time = xmlDoc.CreateElement("DurationTime");
            time.InnerText = b.DurationTime.ToString();

            XmlElement collisionMap = CreateCollisionDataNode(b, xmlDoc);

            XmlElement images = xmlDoc.CreateElement("Images");

            if (b.Images != null && !b.IsSpriteSheet())
            {
                foreach (Image img in b.Images)
                {
                    XmlElement image = xmlDoc.CreateElement("Image");
                    image.InnerText = Convert.ToBase64String(Util.Util.ConvertImageToByte(img));

                    images.AppendChild(image);
                }
            }

            XmlElement idProjDialog = xmlDoc.CreateElement("IdProjDialog");
            idProjDialog.InnerText = b.IdProjDialog.ToString();

            XmlElement wayPointCollection = xmlDoc.CreateElement("WaypointCollection");
            wayPointCollection = CreateWaypointInfoNode(b.Waypoints, wayPointCollection, xmlDoc);

            behaviorElement.AppendChild(idBehavior);
            behaviorElement.AppendChild(nameBehavior);
            behaviorElement.AppendChild(isWalk);
            behaviorElement.AppendChild(spriteSheet);
            behaviorElement.AppendChild(time);
            behaviorElement.AppendChild(images);
            behaviorElement.AppendChild(idProjDialog);
            behaviorElement.AppendChild(wayPointCollection);
            behaviorElement.AppendChild(collisionMap);

            return behaviorElement;
        }

        private XmlElement CreateWaypointInfoNode(WaypointsCollection w, XmlElement waypointElement, XmlDocument xmlDoc)
        {
            XmlElement nameWaypoint = xmlDoc.CreateElement("Name");
            nameWaypoint.InnerText = w.Name;


            XmlElement finalBehavior = xmlDoc.CreateElement("IdFinalBehavior");
            finalBehavior.InnerText = w.IdFinalBehavior.ToString();
            
            XmlElement dialog = xmlDoc.CreateElement("DialogEntity");
            if (w.Dialog != null)
                dialog = CreateDialogInfoNode(w.Dialog, dialog, xmlDoc);

            XmlElement listWayPoints = xmlDoc.CreateElement("Waypoints");
            foreach (Waypoint way in w)
            {
                XmlElement waypoint = xmlDoc.CreateElement("Waypoint");
                
                waypoint = CreateWaypointInfoNode(way, waypoint, xmlDoc);

                listWayPoints.AppendChild(waypoint);
            }

            waypointElement.AppendChild(nameWaypoint);
            waypointElement.AppendChild(finalBehavior);
            waypointElement.AppendChild(dialog);
            waypointElement.AppendChild(listWayPoints);

            return waypointElement;
        }

        private XmlElement CreateWaypointInfoNode(Waypoint way, XmlElement waypointElement, XmlDocument xmlDoc)
        {
            #region Location
            XmlElement point = xmlDoc.CreateElement("Location");

            XmlElement x = xmlDoc.CreateElement("X");
            x.InnerText = way.location.X.ToString();

            XmlElement y = xmlDoc.CreateElement("Y");
            y.InnerText = way.location.Y.ToString();

            point.AppendChild(x);
            point.AppendChild(y);
            #endregion

            XmlElement id = xmlDoc.CreateElement("Id");
            id.InnerText = way.id.ToString();

            XmlElement col = xmlDoc.CreateElement("Col");
            col.InnerText = way.col.ToString();

            XmlElement row = xmlDoc.CreateElement("Row");
            row.InnerText = way.row.ToString();

            XmlElement idCollection = xmlDoc.CreateElement("IdCollection");
            idCollection.InnerText = way.idCollection.ToString();

            XmlElement next = xmlDoc.CreateElement("IdNextWaypoint");
            next.InnerText = way.idNext.ToString();

            XmlElement previous = xmlDoc.CreateElement("IdPreviousWaypoint");
            previous.InnerText = way.idPrevious.ToString();

            waypointElement.AppendChild(id);
            waypointElement.AppendChild(idCollection);
            waypointElement.AppendChild(point);
            waypointElement.AppendChild(col);
            waypointElement.AppendChild(row);
            waypointElement.AppendChild(CreateCharNode(way.character, xmlDoc, true, true));
            waypointElement.AppendChild(next);
            waypointElement.AppendChild(previous);

            return waypointElement;
        }

        private XmlElement CreateDialogInfoNode(DialogEntity d, XmlElement dialogElement, XmlDocument xmlDoc)
        {
            XmlElement id = xmlDoc.CreateElement("Id");
            id.InnerText = d.GetID().ToString();

            XmlElement title = xmlDoc.CreateElement("Title");
            title.InnerText = d.title.Text;

            XmlElement trigger = xmlDoc.CreateElement("Trigger");
            trigger.InnerText = d.trigger.Text;

            XmlElement character = xmlDoc.CreateElement("Character");
            character.InnerText = d.charac.Text;

            XmlElement text = xmlDoc.CreateElement("Text");
            text.InnerText = d.text.Text;

            XmlElement idNext = xmlDoc.CreateElement("IdNextDialog");
            idNext.InnerText = d.idNextDialog.ToString();

            XmlElement titleNextDialog = xmlDoc.CreateElement("TitleNextDialog");
            titleNextDialog.InnerText = d.TitleNextDialog;

            #region Location
            XmlElement location = xmlDoc.CreateElement("Location");

            XmlElement x = xmlDoc.CreateElement("X");
            x.InnerText = d.Location.X.ToString();

            XmlElement y = xmlDoc.CreateElement("Y");
            y.InnerText = d.Location.Y.ToString();

            location.AppendChild(x);
            location.AppendChild(y);
            #endregion

            XmlElement isProject = xmlDoc.CreateElement("IsProject");
            isProject.InnerText = d.IsProject.ToString();

            XmlElement idProj = xmlDoc.CreateElement("IdProject");
            idProj.InnerText = d.idProject.ToString();

            dialogElement.AppendChild(id);
            dialogElement.AppendChild(location);
            dialogElement.AppendChild(title);
            dialogElement.AppendChild(trigger);
            dialogElement.AppendChild(character);
            dialogElement.AppendChild(text);
            dialogElement.AppendChild(idNext);
            dialogElement.AppendChild(titleNextDialog);
            dialogElement.AppendChild(isProject);
            dialogElement.AppendChild(idProj);

            return dialogElement;
        }

        private XmlElement CreateSandAloneBehaviorNode(Tile tile, XmlDocument xmlDoc)
        {
            XmlElement standAloneBehavior = xmlDoc.CreateElement("StandAloneBehaviors");

            for (int k = 0; k < tile.standAloneBehaviors.Count; k++)
            {
                standAloneBehavior.AppendChild(
                    CreateBehaviorInfoNode(tile.standAloneBehaviors[k], xmlDoc.CreateElement("behavior"), xmlDoc));
            }

            return standAloneBehavior;
        }

        private XmlElement SaveLayers(XmlDocument xmlDoc)
        {
            XmlElement layers = xmlDoc.CreateElement("layers");

            for (int k = 0; k < layerPanel.Controls.Count; k++)
            {
                LayerItem item = layerPanel.Controls[k] as LayerItem;
                if (item == null) continue;

                XmlElement layer = xmlDoc.CreateElement("layer");

                XmlElement layerName = xmlDoc.CreateElement("Name");
                layerName.InnerText = item.LayerName.Text;

                XmlElement cols = xmlDoc.CreateElement("cols");
                cols.InnerText = item.Cols.ToString();

                XmlElement rows = xmlDoc.CreateElement("rows");
                rows.InnerText = item.Rows.ToString();

                XmlElement tileWidth = xmlDoc.CreateElement("TileWidth");
                tileWidth.InnerText = item.TileWidth.ToString();

                XmlElement tileHeight = xmlDoc.CreateElement("TileHeight");
                tileHeight.InnerText = item.TileHeight.ToString();

                XmlElement tilesCount = xmlDoc.CreateElement("amount");
                tilesCount.InnerText = item.myLayer.Count.ToString();
                
                XmlElement tiles = xmlDoc.CreateElement("Tiles");

                XmlElement chars = xmlDoc.CreateElement("Characters");
                XmlElement doors = xmlDoc.CreateElement("Doors");
                XmlElement ctiles = xmlDoc.CreateElement("CTiles");
                XmlElement items = xmlDoc.CreateElement("Items");

                for (int j = 0; j < item.myLayer.Count; j++)
                {
                    Tile t = item.myLayer[j];

                    switch (t.myType)
                    {
                        case Tile.TileType.Character:
                            chars.AppendChild(CreateCharNode(t as Character, xmlDoc));
                            break;
                        case Tile.TileType.CommonTile:
                            ctiles.AppendChild(CreateCTileNode(t as CTile, xmlDoc));
                            break;
                        case Tile.TileType.Door:
                            doors.AppendChild(CreateDoorNode(t as Door, xmlDoc));
                            break;
                        case Tile.TileType.Item:
                            items.AppendChild(CreateItemNode(t as Item, xmlDoc));
                            break;

                    }
                }

                tiles.AppendChild(ctiles);
                tiles.AppendChild(chars);
                tiles.AppendChild(doors);
                tiles.AppendChild(items);

                layer.AppendChild(layerName);
                layer.AppendChild(tilesCount);
                layer.AppendChild(cols);
                layer.AppendChild(rows);
                layer.AppendChild(tileWidth);
                layer.AppendChild(tileHeight);
                layer.AppendChild(tiles);
                layers.AppendChild(layer);
            }

            return layers;
        }

        private XmlElement SaveLibrary(XmlDocument xmlDoc)
        {
            XmlElement library = xmlDoc.CreateElement("Library");

            XmlElement chars = xmlDoc.CreateElement("Characters");
            XmlElement doors = xmlDoc.CreateElement("Doors");
            XmlElement tiles = xmlDoc.CreateElement("CTiles");
            XmlElement items = xmlDoc.CreateElement("Items");


            for (int k = 0; k < lib.Controls.Count; k++)
            {
                LibraryItem item = lib.Controls[k] as LibraryItem;
                if (item == null) continue;

                switch (item.Tile.myType)
                {
                    case Tile.TileType.Character:
                        chars.AppendChild(CreateCharNode(item.Tile as Character, xmlDoc, true));
                        break;
                    case Tile.TileType.CommonTile:
                        tiles.AppendChild(CreateCTileNode(item.Tile as CTile, xmlDoc));
                        break;
                    case Tile.TileType.Door:
                        doors.AppendChild(CreateDoorNode(item.Tile as Door, xmlDoc));
                        break;
                    case Tile.TileType.Item:
                        items.AppendChild(CreateItemNode(item.Tile as Item, xmlDoc, true));
                        break;

                }
            }

            library.AppendChild(tiles);
            library.AppendChild(chars);
            library.AppendChild(doors);
            library.AppendChild(items);

            return library;
        }
         
        public bool Save(string path)
        {
            XmlDocument xmlDoc = new XmlDocument();
            XmlElement root = xmlDoc.CreateElement("level");
            
            root.AppendChild(SaveLibrary(xmlDoc));
            root.AppendChild(SaveLayers(xmlDoc));

            xmlDoc.AppendChild(root);

            File.WriteAllText(path, Cryptography.EncryptStringAES(xmlDoc.InnerXml, Cryptography.SHAREDSECRET));
            //File.WriteAllText(path, xmlDoc.InnerXml);

            return true;
        }

        
    }
}
